/*
SteelNzb
Copyright (C) 2008  Wessel v. Staal

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "nzbqueue.h"
#include "global.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>

#ifdef WIN32
#include <windows.h>
#define stricmp _stricmp
#define unlink _unlink
#else
#include <unistd.h>
#define stricmp strcasecmp
#endif

typedef struct nzbQueueItem
{
	nzbFile* file;
	int status;
	int type;
	int partNumber;
	nzbFilePart* part;
} nzbQueueItem;

typedef struct nzbQueueState
{
	nzbQueueItem** queue;
	void* nzbFileState;
	int queueSize;
	pthread_mutex_t queueMutex;
	nzbFile* currentFile;
	void* userData;
	int isEof;
}nzbQueueState;

void nzbqueue_freeQueue(void* nzbQueue)
{
	nzbQueueState* state = nzbQueue;
	int i =0;

	for ( i=0;i<state->queueSize;i++ )
	{
		if ( state->queue[i] != NULL )
		{
			free(state->queue[i]->part);
			free(state->queue[i]);
		}
	}

	free(state->queue);
	free(state);
}

void* nzbqueue_createNzbQueueState(void* nzbState)
{
	nzbQueueState* state = NULL;

	if ( (state = malloc(sizeof(nzbQueueState))) == NULL )
	{
		perror("NzbQueue::createNzbQueueState");
		exit(1);
	}

	memset(state, 0, sizeof(nzbQueueState));

	state->nzbFileState = nzbState;
	
	if ( pthread_mutex_init(&state->queueMutex, NULL) != 0 )
	{
		PRINTERROR("NzbQueue::CreateMutex");
		free(state);
		return 0;
	}

	return state;
}

int nzbqueue_isQueueEmpty(void* queueState)
{
	int i =0 ;
	nzbQueueState* state = queueState;

	for ( i=0;i<state->queueSize;i++ )
	{
		if ( state->queue[i] != NULL )
			return 0;
	}

	return 1;
}

nzbQueueItem* nzbqueue_createItem(nzbFile* file, nzbFilePart* part, char state, char type)
{
	nzbQueueItem* newitem = NULL;

	if ( (newitem = malloc(sizeof(nzbQueueItem))) == NULL )
	{
		perror("NzbQueue::GetQueueItem");
		exit(1);
	}

	memset(newitem, 0, sizeof(nzbQueueItem));

	newitem->file = file;
	newitem->part = part;
	newitem->status = state;
	newitem->type = type;

	return newitem;
}

/**
	Queues item. Expands queue if neccesary
	@param queueState Pointer to queue state
	@param item Pointer to queue item
	@returns void
*/
void nzbqueue_queueItem(void* queueState, nzbQueueItem* item)
{
	nzbQueueState* state = queueState;
	int i =0;
	int found = 0 ;

	for ( i=0; i<state->queueSize;i++ )
	{
		if ( state->queue[i] == NULL )
		{
			state->queue[i] = item;
			found = 1;
			break;
		}
	}

	if ( !found )
	{
		if ( (state->queue = realloc(state->queue, ++state->queueSize * sizeof(nzbQueueItem*))) == NULL )
		{
			perror("NzbQueue::QueueItem");
			exit(1);
		}

		state->queue[state->queueSize-1] = item;
	}
}

/**
	Determines whether the queue has parts related to a particular file
*/
int nzbqueue_hasFileInQueue(void* queueState, void* skip, int internalNum)
{
	int i=0;
	nzbQueueState* state = queueState;

	for ( i=0; i<state->queueSize;i++ )
	{
		if ( state->queue[i] != skip && state->queue[i] != NULL && state->queue[i]->file->internalNum == internalNum )
		{
			return 1;
		}
	}

	return 0;
}

/**
	Gets item from queue
	Thread safe
	@param queuestate Pointer to queue state
	@returns pointer to queue item or NULL when the queue is finished
*/
void* nzbqueue_getQueueItem(void* queueState)
{
	int i=0, j=0;
	nzbQueueItem* ret = NULL;
	nzbQueueState* state = queueState;
	nzbFile* file = NULL;
	nzbFilePart* part = NULL;

	pthread_mutex_lock(&state->queueMutex);

	/* Rules 

		* Reads parts from file on-demand.
		* Parts are encapsulated in queue items
		* When a queue item is returned, mark as being processed in the queue
		* When all parts of a file have been read, put EOF marker on the queue
	*/

	/* read segment from nzb */
	if ( (part = nzb_getNextPart(state->nzbFileState)) == NULL )
	{
		/* out of segments. If the previous call did not mark the state to be finished with the nzb file,
			it means we need to put an EOF marker on the queue. */
		if ( !state->isEof && state->queueSize != 0 )
		{
			nzbQueueItem* item = nzbqueue_createItem(state->currentFile, part, QUEUE_ITEM_STATE_OPEN, QUEUE_ITEM_TYPE_EOF);
			
			/* get the number of parts read for the current file */
			item->partNumber = nzb_getNumPartsRead(state->nzbFileState);

			nzbqueue_queueItem(state, item);
		}

		/* the part was null, it means we need to try and read the next file */
		if ( (file = nzb_getNextFile(state->nzbFileState)) != NULL )
		{
			state->currentFile = file;
			part = nzb_getNextPart(state->nzbFileState);
		}
		else
			state->isEof = 1; /* reading of file failed, this means we reached the end of the nzb file */
	}

	if ( part != NULL )
	{
		nzbQueueItem* item = nzbqueue_createItem(state->currentFile, part, QUEUE_ITEM_STATE_OPEN, QUEUE_ITEM_TYPE_PART);
		item->partNumber = nzb_getNumPartsRead(state->nzbFileState);
		nzbqueue_queueItem(state, item);
	}

	for ( i=0; i<state->queueSize; i++ )
	{
		if ( state->queue[i] != NULL && (state->queue[i]->status == QUEUE_ITEM_STATE_OPEN ||
			state->queue[i]->status == QUEUE_ITEM_STATE_FAILED) )
		{
			if ( state->queue[i]->type == QUEUE_ITEM_TYPE_PART )
			{
				ret = state->queue[i];
				break;
			}
			else if ( state->queue[i]->type == QUEUE_ITEM_TYPE_EOF )
			{
				int found = 0;

				for ( j=0;j<state->queueSize;j++ )
				{
					if ( state->queue[j] != NULL && state->queue[j]->file->internalNum == state->queue[i]->file->internalNum &&
						state->queue[j] != state->queue[i] )
					{
						found = 1;
						break;
					}
				}

				if ( !found )
				{
					ret = state->queue[i];
					break;
				}
			}
		}
	}

	if ( ret != NULL )
		ret->status = QUEUE_ITEM_STATE_PROCESSING;

	pthread_mutex_unlock(&state->queueMutex);

	return ret;
}

/**
	Sets status of a particular item in the queue
	Thread safe
	@param queuestate Pointer to queue
	@param item Pointer to queue item
	@param status Status of item
	@returns void
*/
void nzbqueue_setQueueItemStatus(void* queueState, void* item, int status)
{
	int i=0;
	nzbQueueState* state = queueState;

	/* Rules
		
			* When item is marked FAILED or SUCCESS, free the item and clear its location in the queue
			* When the item type is an EOF marker, free up the related file structure
			* When the item type is a part and the passed status is FAILED,
					when there is a EOF marker available for the related file,
					mark it as failed to indicate one of the parts of the file failed.
	*/

	pthread_mutex_lock(&state->queueMutex);

	for ( i=0; i<state->queueSize;i++ )
	{
		if ( state->queue[i] == item )
		{
			switch ( status )
			{
				case QUEUE_ITEM_STATE_SUCCESS:
				case QUEUE_ITEM_STATE_FAILED:
				{
					if ( state->queue[i]->type == QUEUE_ITEM_TYPE_EOF )
						free(state->queue[i]->file);

					if ( status == QUEUE_ITEM_STATE_FAILED )
					{
						int j =0;

						for ( j=0; j<state->queueSize; j++ )
						{
							if ( state->queue[j] != NULL && state->queue[j]->type == QUEUE_ITEM_TYPE_EOF &&
								state->queue[j]->file->internalNum == state->queue[i]->file->internalNum )
							{
								state->queue[j]->status = QUEUE_ITEM_STATE_FAILED;
								break;
							}
						}
					}

					free(state->queue[i]->part);
					free(state->queue[i]);

					state->queue[i] = NULL;

					break;
				}
			}
		}
	}

	pthread_mutex_unlock(&state->queueMutex);
}

nzbFile* nzbqueue_getQueueItemFile(void* queueState, void* item)
{
	nzbQueueState* state = queueState;
	nzbQueueItem* queueitem = item;
	nzbFile* ret = NULL;

	pthread_mutex_lock(&state->queueMutex);

	ret = queueitem->file ;

	pthread_mutex_unlock(&state->queueMutex);

	return ret;
}

int nzbqueue_getQueueItemPartNumber(void* queueState, void* item)
{
	nzbQueueState* state = queueState;
	nzbQueueItem* queueitem = item;
	int ret = 0;

	pthread_mutex_lock(&state->queueMutex);

	ret = queueitem->partNumber ;

	pthread_mutex_unlock(&state->queueMutex);

	return ret;
}

nzbFilePart* nzbqueue_getQueueItemFilePart(void* queueState, void* item)
{
	nzbQueueState* state = queueState;
	nzbQueueItem* queueitem = item;
	nzbFilePart* ret = NULL;

	pthread_mutex_lock(&state->queueMutex);

	ret = queueitem->part ;

	pthread_mutex_unlock(&state->queueMutex);

	return ret;
}

int nzbqueue_getQueueItemType(void* queueState, void* item)
{
	nzbQueueState* state = queueState;
	nzbQueueItem* queueitem = item;
	int ret = 0;

	pthread_mutex_lock(&state->queueMutex);

	ret = queueitem->type;

	pthread_mutex_unlock(&state->queueMutex);

	return ret;

}
int nzbqueue_getQueueItemStatus(void* queueState, void* item)
{
	nzbQueueState* state = queueState;
	nzbQueueItem* queueitem = item;
	int ret = 0;

	pthread_mutex_lock(&state->queueMutex);

	ret = queueitem->status;

	pthread_mutex_unlock(&state->queueMutex);

	return ret;
}

void nzbqueue_setUserData(void* queueState, void* userdata)
{
	nzbQueueState* state = queueState;

	pthread_mutex_lock(&state->queueMutex);

	state->userData = userdata;

	pthread_mutex_unlock(&state->queueMutex);
}
